package Zim::Components::TreeView;

use strict;
use vars '$AUTOLOAD';
use Gtk2;
use Gtk2::Gdk::Keysyms;

our $VERSION = '0.15';

=head1 NAME

Zim::Components::TreeView - Page index widgets

=head1 DESCRIPTION

This module contains the widgets to display the index 
of a zim repository as a TreeView.

=head1 METHODS

Undefined methods AUTOLOAD to the L<Gtk2::TreeView> widget.

=over 4

=item C<new(app => PARENT)>

Simple constructor.

=item C<init()>

Method called by the constructor.

=cut

my ($k_left, $k_right) = @Gtk2::Gdk::Keysyms{qw/Left Right/};

sub new {
	my $class = shift;
	my $self = bless {@_}, $class;
	$self->init();
	return $self;
}

sub init { # called by new()
	my $self = shift;

	my $scroll_window = Gtk2::ScrolledWindow->new();
	$scroll_window->set_policy('automatic', 'automatic');
	$scroll_window->set_shadow_type('in');
	$self->{scroll_window} = $scroll_window;

	my $tree_store = Gtk2::TreeStore->new('Glib::String', 'Glib::String');
#	$tree_store->signal_connect(row_changed => \&on_row_changed, $self);
	$self->{tree_store} = $tree_store;

	my $tree_view = Gtk2::TreeView->new($tree_store);
	my $renderer = Gtk2::CellRendererText->new();
	$renderer->signal_connect_swapped(edited => \&on_cell_edited, $self);
	$self->{renderer} = $renderer;
	my $page_column = Gtk2::TreeViewColumn->new_with_attributes(
        	'Pages', $renderer, 'text', 0);
	$tree_view->append_column($page_column);
	$tree_view->set_headers_visible(0);
#	$tree_view->set_reorderable(1);
	$tree_view->get_selection->set_mode('browse'); # one item selected at all times
	$tree_view->signal_connect(key_press_event      => \&on_key_press_event);
	$tree_view->signal_connect(button_release_event => \&on_button_release_event, $self);
	$tree_view->signal_connect(row_activated        => \&on_row_activated, $self);
	$tree_view->signal_connect(popup_menu           => sub {
			my ($path) = $tree_view->get_cursor;
			$self->popup_context_menu($path);
		} );
	$scroll_window->add($tree_view);
	$self->{tree_view} = $tree_view;

	$self->{app}->signal_connect(page_loaded => sub {$self->select_page(pop)});
	$self->{app}->signal_connect(page_deleted => sub {$self->unlist_page(pop)});
	$self->{app}->signal_connect(page_created => sub {
		my $page = pop;
		$self->list_page($page);
		$self->select_page($page);
	} );
}

sub AUTOLOAD {
	my $self = shift;
	$AUTOLOAD =~ s/^.*:://;
	return if $AUTOLOAD eq 'DESTROY';
	return $self->{tree_view}->$AUTOLOAD(@_);
}
	
=item C<widget()>

Returns the root widget. This should be used to add the object to a container widget.
Also use this widget for things like show_all() and hide_all().

=cut

sub widget { return $_[0]->{scroll_window} }

sub on_key_press_event { # some extra keybindings for the treeview
	my ($tree_view, $event) = @_;
	my $val = $event->keyval();
	my $key = chr($val);
	#print "got key $key ($val)\n";
	if ($val == $k_left) { # Left arrow
		my ($path) = $tree_view->get_selection->get_selected_rows;
		$tree_view->collapse_row($path) if defined $path;
	}
	elsif ($val == $k_right) { # Rigth arrow
		my ($path) = $tree_view->get_selection->get_selected_rows;
		$tree_view->expand_row($path, 0) if defined $path;
	}
	else { return 0 }
	return 1;
}

sub on_button_release_event {
	my ($tree_view, $event, $self) = @_;
	return 0 if $event->type ne 'button-release';
	
	my ($x, $y) = $event->get_coords;
	my ($path, $column) = $tree_view->get_path_at_pos($x, $y);
	return 0 unless $path;
	
	$self->activate_if_selected($path, $column)
		if $event->button == 1;
	$self->popup_context_menu($path, $event)
		if $event->button == 3;
	return 0;
}

sub on_row_activated { # load a page when clicked or entered
	my $self = pop;
	my ($tree_view, $path) = @_;
	my $page = $self->path2page($path);
	$self->{app}->load_page($page)
		unless $page eq $self->{app}{page}->name;
}

sub on_row_changed { # Used to control drag-n-drop
	my ($model, $path, $iter, $self) = @_;
	my $parent = $model->iter_parent($iter);
	$parent = $parent ? $model->get($parent, 1) : '' ;
	my ($base, $page) = $model->get($iter);
	return if $page eq $parent.':'.$base;
	my $ok = $self->{app}->rename_page($page => $parent.':'.$base);
	return if $ok;

	# TODO put data back if move failed
	
}

=item C<page2path(PAGE)>

Returns the TreePath corresponding to PAGE.

=cut

sub page2path {
	my ($self, $page) = @_;
	$page =~ s/^:+|:+$//g;
	my @page = split /:+/, $page;
	my $model = $self->{tree_view}->get_model;
	my $iter;
	for my $p (@page) {
		$iter = $model->iter_children($iter) || return;
			# returns root iter when iter is undef
		while ($model->get($iter, 0) ne $p) {
			$iter = $model->iter_next($iter) || return;
		}
	}
	return $iter ? $model->get_path($iter) : undef ;
}

=item C<path2page(PATH)>

Returns the page name corresponding to a certain TreePath.

=cut

sub path2page {
	my ($self, $path) = @_;
	my $model = $self->{tree_view}->get_model;
	my $iter = $model->get_iter($path);
	return $model->get($iter, 1);
}

=item C<activate_if_selected(PATH, COLUMN)>

Emit the "row_activated" signal if PATH was already selected.
This method is used for the single-click navigation of the TreeView.

COLUMN is an optional argument.

=cut

sub activate_if_selected { # single-click navigation
	my ($self, $path, $column) = @_;
	my $tree_view = $self->{tree_view};

	my ($selected) = $tree_view->get_selection->get_selected_rows;
	return 0 unless $selected and 0 == ($path->compare($selected));

	my $page = $self->path2page($path);
	$column ||= $tree_view->get_column(0);
	$tree_view->row_activated($path, $column)
		unless $page eq $self->{app}{page}->name;
}

=item C<popup_context_menu(PATH, EVENT)>

Show the context menu for TreePath PATH.

EVENT is an optional argument.

=cut

sub popup_context_menu {
	my ($self, $path, $event) = @_;
	my $tree_view = $self->{tree_view};
	my $page = $self->path2page($path);
	
	my $menu = Gtk2::Menu->new();
	my $item0 = Gtk2::MenuItem->new('Copy page name');
	$item0->signal_connect(activate => sub {
		my $clipboard = Gtk2::Clipboard->get(
			Gtk2::Gdk::Atom->new('PRIMARY') );
		$clipboard->set_text($page);
	} );
	$menu->append($item0);

	unless ($self->{app}{settings}{read_only}) {
		my $item1 = Gtk2::MenuItem->new('Rename page');
		$item1->signal_connect(activate =>
			sub { $self->{app}->rename_page($page) });
		$menu->append($item1);
		my $item2 = Gtk2::MenuItem->new('Delete page');
		$item2->signal_connect(activate =>
			sub { $self->{app}->delete_page($page) });
		$menu->append($item2);
	}
	$menu->show_all;

	my ($button, $time) = $event
		? ($event->button, $event->time)
		: (0, 0) ;
	#my $pos_func = $event ? undef : sub {
	#};
	$menu->popup(undef, undef, undef, undef, $button, $time);
	return 1; # needed for popup_menu signal (shift-F10), but why ?
}

=item C<load_index()>

Called the first time the pane is showed to fill the index tree.

Will be DEPRECATED.

=cut

sub load_index { # TODO remove this from Zim.pm and PageView.pm
	my $self = shift;
	return if $self->{_loaded};
	$self->{tree_store}->clear; # just to be sure
	eval { $self->list_pages(':') };
	warn $@ if $@;
	$self->select_page($self->{app}{page}->name);
	$self->{app}->message("Index loaded");
	$self->{_loaded} = 1;
}

=item C<list_pages(NAMESPACE)>

Wrapper around C<< Zim::Repository->list_pages() >> to fill the tree.

=cut

sub list_pages {
	my ($self, $namespace, $iter) = @_; # last argument is private
	#warn "list_pages: $namespace\n";
	$self->{tree_store}->signal_handlers_block_by_func(\&on_row_changed);
	$iter ||= $self->list_page($namespace);
	$namespace =~ s/:?$/:/;
	my $model = $self->{tree_view}->get_model;
	# TODO check for existing children
	for my $p ($self->{app}{repository}->list_pages($namespace)) {
		#warn "\t$p\n";
		my $is_dir = ($p =~ s/:+$//);
		my $child = $model->append($iter);
		$model->set($child, 0 => $p, 1 => $namespace.$p); # base_name, name
		$self->list_pages($namespace.$p, $child) if $is_dir; # recurse
	}
	$self->{tree_store}->signal_handlers_unblock_by_func(\&on_row_changed);
}

=item C<list_page(REALNAME)>

Adds a page to the index tree.

=cut

sub list_page {
	my ($self, $page) = @_;
	$page =~ s/^:+|:+$//g;
	my @page = split /:+/, $page;
	$page = '';
	my $model = $self->{tree_view}->get_model;
	$model->signal_handlers_block_by_func(\&on_row_changed);
	my $iter;
	for my $p (@page) {
		$page .= ':'.$p;
		my $child = $model->iter_children($iter);
			# returns root iter when iter is undef
		while ($child and $model->get($child, 0) ne $p) {
			$child = $model->iter_next($child);
		}
		unless ($child) {
			$child = $model->append($iter);
			$model->set($child, 0 => $p, 1 => $page); # base_name, name
		}
		$iter = $child;
	}
	$model->signal_handlers_unblock_by_func(\&on_row_changed);
	return $iter;
}

=item C<unlist_page(REALNAME)>

Removes a page from the index unless it has children.

=cut

sub unlist_page {
	my ($self, $page) = @_;
	my $path = $self->page2path($page) || return;
	my $model = $self->{tree_view}->get_model;
	my $iter = $model->get_iter($path);
	return if $model->iter_children($iter); # has children
	$model->remove($iter);
}

=item C<select_page(PAGE)>

Update the TreeView to display and highlight a certain page.

=cut

sub select_page {
	my ($self, $page) = @_;
	my $path = $self->page2path($page) || return;
	my $view = $self->{tree_view};
	$view->expand_to_path($path);
	$view->get_selection->select_path($path);
	$view->set_cursor($path);
	$view->scroll_to_cell($path, undef, 0); # vert. scroll
}

1;

__END__

=back

=head1 AUTHOR

Jaap Karssenberg (Pardus) E<lt>pardus@cpan.orgE<gt>

Copyright (c) 2005 Jaap G Karssenberg. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Zim>

=cut

